/*
* This file is part of Rectball.
* Copyright (C) 2015 Dani RodrÃguez.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package es.danirod.rectball;
import com.badlogic.gdx.*;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.assets.loaders.BitmapFontLoader.BitmapFontParameter;
import com.badlogic.gdx.assets.loaders.TextureLoader.TextureParameter;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.*;
import com.badlogic.gdx.utils.I18NBundle;
import com.badlogic.gdx.utils.ScreenUtils;
import es.danirod.rectball.model.GameState;
import es.danirod.rectball.model.Statistics;
import es.danirod.rectball.platform.Platform;
import es.danirod.rectball.scene2d.RectballSkin;
import es.danirod.rectball.screens.*;
import java.nio.ByteBuffer;
import java.util.*;
/**
* Main class for the game.
*/
public class RectballGame extends Game {
private final Platform platform;
/* FIXME: Privatize this. */
private final Map<Integer, AbstractScreen> screens = new HashMap<>();
private final GameState currentGame;
private final Deque<AbstractScreen> screenStack = new ArrayDeque<>();
public Statistics statistics;
public AssetManager manager;
public SoundPlayer player;
private RectballSkin uiSkin;
private TextureAtlas ballAtlas;
private I18NBundle locale;
/** Whether the game is restoring state from an Android kill or not. */
private boolean restoredState;
/**
* Create a new instance of Rectball.
*
* @param platform the platform this game is using.
* @param state
*/
public RectballGame(Platform platform, GameState state) {
this.platform = platform;
this.currentGame = state;
this.restoredState = true;
}
public RectballGame(Platform platform) {
this.platform = platform;
this.currentGame = new GameState();
this.restoredState = false;
}
/**
* Get the common platform facade. This lets the application logic request
* the platform to do special things such as sharing the score or sending
* information.
*
* @return the platform this game is using.
*/
public Platform getPlatform() {
return platform;
}
@Override
public void create() {
if (Constants.DEBUG) {
Gdx.app.setLogLevel(Application.LOG_DEBUG);
}
// Add the screens.
addScreen(new GameScreen(this));
addScreen(new GameOverScreen(this));
addScreen(new MainMenuScreen(this));
addScreen(new SettingsScreen(this));
addScreen(new LoadingScreen(this));
addScreen(new LoadingBackScreen(this));
addScreen(new StatisticsScreen(this));
addScreen(new AboutScreen(this));
addScreen(new TutorialScreen(this));
// Load the resources.
manager = createManager();
screens.get(Screens.LOADING).load();
if (!restoredState) {
setScreen(screens.get(Screens.LOADING));
} else {
setScreen(screens.get(Screens.LOADING_BACK));
}
}
public void finishLoading() {
// Load the remaining data.
platform.score().readData();
statistics = platform.statistics().loadStatistics();
player = new SoundPlayer(this);
uiSkin = new RectballSkin(this);
updateBallAtlas();
locale = setUpLocalization();
// Load the screens.
for (Map.Entry<Integer, AbstractScreen> screen : screens.entrySet()) {
screen.getValue().load();
}
// Enter main menu.
pushScreen(Screens.MAIN_MENU);
// If we are restoring the game, push also the game screen.
// Keep the main menu screen in the stack, we are going to need it
// when we finish the game.
if (restoredState) {
pushScreen(Screens.GAME);
}
}
public boolean isRestoredState() {
return restoredState;
}
private I18NBundle setUpLocalization() {
FileHandle baseFileHandle = Gdx.files.internal("locale/rectball");
return I18NBundle.createBundle(baseFileHandle);
}
public I18NBundle getLocale() {
return locale;
}
private AssetManager createManager() {
AssetManager manager = new AssetManager();
// Set up the parameters for loading linear textures. Linear textures
// use a linear filter to not have artifacts when they are scaled.
TextureParameter texParameters = new TextureParameter();
BitmapFontParameter fntParameters = new BitmapFontParameter();
texParameters.minFilter = texParameters.magFilter = TextureFilter.Linear;
fntParameters.minFilter = texParameters.magFilter = TextureFilter.Linear;
// Load game assets.
manager.load("logo.png", Texture.class, texParameters);
manager.load("board/normal.png", Texture.class, texParameters);
manager.load("board/colorblind.png", Texture.class, texParameters);
// Load UI resources.
manager.load("ui/progress.png", Texture.class, texParameters);
manager.load("ui/icons.png", Texture.class, texParameters);
manager.load("ui/yellow_patch.png", Texture.class);
manager.load("ui/switch.png", Texture.class, texParameters);
manager.load("fonts/bold.fnt", BitmapFont.class, fntParameters);
manager.load("fonts/monospace.fnt", BitmapFont.class);
manager.load("fonts/monospaceOutline.fnt", BitmapFont.class);
manager.load("fonts/normal.fnt", BitmapFont.class, fntParameters);
manager.load("fonts/small.fnt", BitmapFont.class, fntParameters);
// Load sounds
manager.load("sound/fail.ogg", Sound.class);
manager.load("sound/game_over.ogg", Sound.class);
manager.load("sound/perfect.ogg", Sound.class);
manager.load("sound/select.ogg", Sound.class);
manager.load("sound/success.ogg", Sound.class);
manager.load("sound/unselect.ogg", Sound.class);
return manager;
}
@Override
public void dispose() {
manager.dispose();
}
/**
* Pushes the provided screen into the stack and sets it as the current screen.
* The screen that has been previously on screen can be retrieved later using
* popScreen.
*
* @param id the screen that should be visible now.
* @since 0.3.0
*/
public void pushScreen(int id) {
screenStack.push(screens.get(id));
setScreen(screenStack.peek());
}
/**
* Pops the current screen from the stack. The screen that was visible before
* pushing the current screen is the one that would be visible. If no screens
* are in the stack, the main menu screen will be visible.
*
* @since 0.3.0
*/
public void popScreen() {
screenStack.removeFirst();
if (screenStack.isEmpty()) {
pushScreen(Screens.MAIN_MENU);
} else {
setScreen(screenStack.peek());
}
}
/**
* Clears the stack of screens. Every screen in the stack is removed and
* the main menu gets as the current screen visible.
*
* @since 0.3.0
*/
public void clearStack() {
screenStack.clear();
setScreen(screens.get(Screens.MAIN_MENU));
}
/**
* Add a screen to the map of Strings.
*
* @param screen the screen being added to the map
*/
private void addScreen(AbstractScreen screen) {
screens.put(screen.getID(), screen);
}
/**
* Get the skin used by Scene2D UI to display things.
*
* @return the skin the game should use.
*/
public RectballSkin getSkin() {
return uiSkin;
}
public GameState getState() {
return currentGame;
}
public void updateBallAtlas() {
boolean isColorblind = platform.preferences().getBoolean("colorblind");
String ballsTexture = isColorblind ? "board/colorblind.png" : "board/normal.png";
Texture balls = manager.get(ballsTexture);
TextureRegion[][] regions = TextureRegion.split(balls, 256, 256);
ballAtlas = new TextureAtlas();
ballAtlas.addRegion("ball_red", regions[0][0]);
ballAtlas.addRegion("ball_yellow", regions[0][1]);
ballAtlas.addRegion("ball_blue", regions[1][0]);
ballAtlas.addRegion("ball_green", regions[1][1]);
ballAtlas.addRegion("ball_gray", regions[1][2]);
}
/**
* Create a screenshot and return the generated Pixmap. Since the game
* goes yUp, the returned screenshot is flipped so that it's correctly
* yDown'ed.
*
* @return game screenshot
*/
public Pixmap requestScreenshot(int x, int y, int width, int height) {
Gdx.app.log("Screenshot", "Requested a new screenshot of size " + width + "x" + height);
Pixmap screenshot = ScreenUtils.getFrameBufferPixmap(x, y, width, height);
// Since the game runs using yDown, the screen has to be flipped.
ByteBuffer data = screenshot.getPixels();
byte[] flippedBuffer = new byte[4 * width * height];
int bytesPerLine = 4 * width;
for (int j = 0; j < height; j++) {
data.position(bytesPerLine * (height - j - 1));
data.get(flippedBuffer, j * bytesPerLine, bytesPerLine);
}
data.clear();
data.put(flippedBuffer);
data.clear();
return screenshot;
}
public TextureAtlas getBallAtlas() {
return ballAtlas;
}
public AbstractScreen getScreen(int id) {
return screens.get(id);
}
public void setRestoredState(boolean restoredState) {
this.restoredState = restoredState;
}
}